using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using System.Xml;
using System.Data.Common;
using System.Windows.Forms;
using MacomberMap.Common.Convertors;
using MacomberMap.Common.Internals;
using MacomberMap.Common.Types;
using MacomberMap.Common.User_Interfaces.Components;

namespace MacomberMap.Common.Components
{
    /// <summary>
    /// This class holds information on a boundary (county/state)
    /// </summary>
    [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]    
    public class MM_Boundary : MM_Element
    {
        #region Variable Declarations
        /// <summary>The largest points within the boundary</summary>
        public PointF Max = new PointF(float.NaN, float.NaN);

        /// <summary>The smallest points within the boundary</summary>
        public PointF Min = new PointF(float.NaN, float.NaN);

        /// <summary>The centroid of the boundary</summary>
        public PointF Centroid = new PointF(float.NaN, float.NaN);

        /// <summary>The series of coordinates that comprise the boundary</summary>
        public PointF[] Coordinates;

        /// <summary>The website (if any) for the boundary</summary>
        public String Website;

        /// <summary>The graphics path used for hit testing by latitude/longitude</summary>
        private GraphicsPath HitTestPath = new GraphicsPath();

        /// <summary>The index of the county5n</summary>
        public int Index;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a blank MM Boundary
        /// </summary>
        public MM_Boundary()
        {
            this.ElemType = MM_Overall_Display.ElemTypes["County"];
        }

        /// <summary>
        /// Initialize an MM Boundary from XML
        /// </summary>
        /// <param name="BaseElement"></param>
        public MM_Boundary(XmlElement BaseElement)
        {
            this.Name = BaseElement.Attributes["Name"].Value;            
            this.Website = BaseElement.Attributes["Website"].Value;
            this.ElemType = MM_Overall_Display.FindElementType("County");                
            if (this.Name == "STATE")
            {
                MM_Repository.StateBoundary = this;
                MM_Repository.Counties.Add(this.Name, this);
                this.ElemType = MM_Overall_Display.FindElementType("State");
            }
            else
            {
                MM_Repository.Counties.Add(this.Name, this);
                this.ElemType = MM_Overall_Display.FindElementType("County");                
            }

            //Pull in our coordinates
            string[] splCoords = BaseElement.Attributes["Coordinates"].Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            List<PointF> ConvertedPoints = new List<PointF>(splCoords.Length / 2);
            Coordinates = new PointF[splCoords.Length / 2];
            for (int a = 0; a < splCoords.Length; a += 2)
            {
                Coordinates[a / 2] = new PointF(MM_Repository.ToSingle(splCoords[a]), MM_Repository.ToSingle(splCoords[a + 1]));
                ConvertedPoints.Add(new PointF(MM_Repository.ToSingle(splCoords[a]) * 1000f, MM_Repository.ToSingle(splCoords[a + 1]) * 1000f));
            }
            HitTestPath.AddLines(ConvertedPoints.ToArray());
            ConvertedPoints.Clear();
            
        }

        /// <summary>
        /// Create a new boundary from a data reader
        /// </summary>
        /// <param name="DataReader">The data reader containing the boundary's parameters</param>
        public MM_Boundary(DbDataReader DataReader)
        {
            this.ElemType = MM_Overall_Display.ElemTypes["County"];
            this.Name = Convert.ToString(DataReader["NAME"]);
            this.Max = new PointF(Convert.ToSingle(DataReader["MAX_X"]), Convert.ToSingle(DataReader["MAX_Y"]));
            this.Min = new PointF(Convert.ToSingle(DataReader["Min_X"]), Convert.ToSingle(DataReader["Min_Y"]));
            this.Centroid = new PointF(Convert.ToSingle(DataReader["Centroid_X"]), Convert.ToSingle(DataReader["Centroid_Y"]));
            this.Website = Convert.ToString(DataReader["WEBSITE"]);
            this.Index = Convert.ToInt32(DataReader["Index"]);
            byte[] inBytes = (byte[])DataReader["COORDINATES"];
            
            //Determine if our first and last coordinates match. If not, we'll need to adjust them.
            bool LastCoordinateNeedsDuplication = false;
            for (int a = 0; a < 8; a++ )
                if (inBytes[a] != inBytes[inBytes.Length + a - 9])
                    LastCoordinateNeedsDuplication = true;
            
            Coordinates = new PointF[(inBytes.Length / 8) + (LastCoordinateNeedsDuplication ? 1 : 0) ];
            List<PointF> ConvertedPoints = new List<PointF>(Coordinates.Length);
            for (int a = 0; a < inBytes.Length; a += 8)
            {
                Coordinates[a / 8] = new PointF(BitConverter.ToSingle(inBytes, a), BitConverter.ToSingle(inBytes, a+4));
                ConvertedPoints.Add(new PointF(Coordinates[a / 8].X * 1000f, Coordinates[a / 8].Y * 1000f));
            }

            if (LastCoordinateNeedsDuplication)
            {
                Coordinates[Coordinates.Length - 1] = Coordinates[0];
                ConvertedPoints.Add(new PointF(Coordinates[0].X * 1000f, Coordinates[0].Y * 1000f));
            }
                
            HitTestPath.AddLines(ConvertedPoints.ToArray());
            ConvertedPoints.Clear();

            if (this.Name == "STATE")
            {
                MM_Repository.StateBoundary = this;
                this.ElemType = MM_Overall_Display.ElemTypes["State"];
            }
            else
            {                
                MM_Repository.Counties.Add(this.Name, this);
                this.ElemType = MM_Overall_Display.ElemTypes["County"];
            }
        }
        #endregion

        #region Hit testing
        /// <summary>
        /// Test to see whether the specified lat/long is within this boundary
        /// </summary>
        /// <param name="TestPoint"></param>
        /// <returns></returns>
        public bool HitTest(PointF TestPoint)
        {
            if ((TestPoint.X < Min.X) || (TestPoint.X > Max.X) || (TestPoint.Y < Min.Y) || (TestPoint.Y > Max.Y))
                return false;
            else
                return HitTestPath.IsVisible(new PointF(TestPoint.X * 1000f, TestPoint.Y * 1000f));
        }
        #endregion

        /// <summary>
        /// Show the name of the boundary
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Name;
        }

        /// <summary>
        /// Return the list of substations within this boundary
        /// </summary>
        public MM_Substation[] Substations
        {
            get
            {
                List<MM_Substation> Subs = new List<MM_Substation>();
                foreach (MM_Substation Sub in MM_Repository.Substations.Values)
                    if (HitTest(Sub.LatLong))
                        Subs.Add(Sub);
                return Subs.ToArray();
            }
        }



        /// <summary>
        /// Recompute our min, max, centroid based on our coordinates.
        /// </summary>
        public void RecomputeExtrema()
        {
            lock (this)
            {
                //Initialize our variables
                Min = new PointF(float.NaN, float.NaN);
                Max = new PointF(float.NaN, float.NaN);
                Centroid = new PointF(0, 0);

                //Iterate through all points, and update
                foreach (PointF LatLng in this.Coordinates)
                {
                    if (float.IsNaN(Max.X) || Max.X < LatLng.X) Max.X = LatLng.X;
                    if (float.IsNaN(Max.Y) || Max.Y < LatLng.Y) Max.Y = LatLng.Y;
                    if (float.IsNaN(Min.X) || Min.X > LatLng.X) Min.X = LatLng.X;
                    if (float.IsNaN(Min.Y) || Min.Y > LatLng.Y) Min.Y = LatLng.Y;
                    Centroid.X += LatLng.X;
                    Centroid.Y += LatLng.Y;
                }

                Centroid.X /= (float)this.Coordinates.Length;
                Centroid.Y /= (float)this.Coordinates.Length;
            }
        }
    }

}
